package game.games.rpg;

import game.GameConfig;
import game.NotResizable;
import game.console.IConsoleCommand;
import game.console.IConsoleStream;
import game.console.MessageConsoleStream;
import game.games.rpg.magic.EnterFireBuff;
import game.games.rpg.magic.EnterWaterBuff;
import game.gfx.AbstractImageGeneratingGame;
import game.gfx.ColoredChar;
import game.util.Logger;
import game.util.Util;

import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.filechooser.FileSystemView;

@NotResizable
public class RPG extends AbstractImageGeneratingGame
{
    private static Font       FONT; 
    static
    {
        try
        {
            Font f = Font.createFont(Font.TRUETYPE_FONT, RPG.class.getResourceAsStream("/DejaVuSansMono.ttf"));
            FONT = f.deriveFont(Font.PLAIN, 20);
        }
        catch (Exception ex)
        {
            Logger.showExceptionError(ex);
        }
        if (FONT == null)
        {
            FONT = new Font("Courier New", Font.PLAIN, 18);
        }
    }
    
    private static final boolean WALK_ON_ARROWKEY_INPUT   = false;
    
    private static final char    ACTION_NOTHING           = 0x0;
    private static final char    ACTION_ATTACK            = 'a';
    private static final char    ACTION_OPEN              = 'o';
    private static final char    ACTION_RUN               = 'r';
    private static final char    ACTION_WALK              = 'w';
    private char                 action                   = ACTION_NOTHING;
    
    private Interface            ui;
    private World                world;
    
    private int                  TILE_WIDTH;
    private int                  TILE_HEIGHT;
    private int                  TILE_HEIGHT_RENDER_MINUS = 4;
    
    private int                  tick                     = 0;
    private int                  lastTick                 = 0;
    private final int            TICK_FACTOR              = 5;
    
    private final static String  CHARSET                  = "UTF-8";
    
    // Override
    private static final String  NAME                     = "flying dick from outa space - Paule Preview";
    
    @Override
    protected String getName()
    {
        return NAME;
    }
    
    @Override
    protected IMAGE_POSITION getImagePosition()
    {
        return IMAGE_POSITION.TOP_LEFT;
    }
    
    @Override
    protected void initConfig()
    {
        getConfig().setInt(GameConfig.DESIRED_FPS, 36);
        getConfig().setTrue(GameConfig.CONSOLE_ENABLED);
        // console
        getConsole().addConsoleCommand(new IConsoleCommand()
        {
            @Override
            public String[] getSynonym()
            {
                return new String[] { "cf" };
            }
            
            @Override
            public String getInfo()
            {
                return "change font";
            }
            
            @Override
            public String getCommand()
            {
                return "change-font";
            }
            
            @Override
            public IConsoleStream fireCommand(String[] args)
            {
                String name  = args[1];
                int style = Integer.parseInt(args[2]);
                int size  = Integer.parseInt(args[3]);
                FONT = new Font(name, style, size);
                return new MessageConsoleStream("new font: " + FONT);
            }
        });
        getConsole().addConsoleCommand(new IConsoleCommand()
        {
            @Override
            public String[] getSynonym()
            {
                return new String[] { "em" };
            }
            
            @Override
            public String getInfo()
            {
                return "explore the map";
            }
            
            @Override
            public String getCommand()
            {
                return "explore";
            }
            
            @Override
            public IConsoleStream fireCommand(String[] args)
            {
                if (world.getCurrentRoom().isClearExplored())
                {
                    world.getCurrentRoom().exploreAll();
                }
                return new MessageConsoleStream("explored");
            }
        });
        getConsole().addConsoleCommand(new IConsoleCommand()
        {
            @Override
            public String[] getSynonym()
            {
                return new String[] { "se" };
            }
            
            @Override
            public String getInfo()
            {
                return "see everything";
            }
            
            @Override
            public String getCommand()
            {
                return "see-everything";
            }
            
            @Override
            public IConsoleStream fireCommand(String[] args)
            {
                if (world.getCurrentRoom().isSeeEverything() == false)
                {
                    world.getCurrentRoom().setSeeEverything(true);
                }
                return new MessageConsoleStream("see everything");
            }
        });
        getConsole().addConsoleCommand(new IConsoleCommand()
        {
            
            @Override
            public String[] getSynonym()
            {
                return new String[] { "cvr" };
            }
            
            @Override
            public String getInfo()
            {
                return "change view-radius of player";
            }
            
            @Override
            public String getCommand()
            {
                return "change-view-radius";
            }
            
            @Override
            public IConsoleStream fireCommand(String[] args)
            {
                String message = "invalid arguments " + args.length;
                if (args.length == 2)
                {
                    try
                    {
                        int newRadius = Integer.parseInt(args[1]);
                        message = "change view-radius from " + world.getCurrentRoom().getViewRadius() + " to " + newRadius;
                        world.getCurrentRoom().setViewRadius(newRadius);
                    }
                    catch (Exception e)
                    {
                        message = "illegal argument: " + args[1];
                    }
                }
                return new MessageConsoleStream(message);
            }
        });
        // keylistener
        getGameListener().addKeyListener(new KeyAdapter()
        {
            @Override
            public void keyPressed(KeyEvent e)
            {
                try
                {
                    switch (e.getKeyCode())
                    {
                        // special keystroke ctrl+l to load map, if +alt a file chooser opens
                        case KeyEvent.VK_L:
                        {
                            if (e.isControlDown())
                            {
                                if (e.isShiftDown())
                                {
                                    loadMap(e.isAltDown());
                                }
                                else
                                {
                                    load();
                                }
                            }
                            break;
                        }
                        // special keystroke ctrl+s to save, if +alt a input dialog to enter a name opens
                        case KeyEvent.VK_S:
                        {
                            if (e.isControlDown())
                            {
                                save(e.isAltDown());
                            }
                            break;
                        }
                    }
                }
                catch (Exception ex)
                {
                    Logger.showExceptionError(ex);
                }
            }
        });
        // game controls
        getGameListener().addKeyListener(new KeyAdapter()
        {
            @Override
            public void keyPressed(KeyEvent e)
            {
                switch (e.getKeyCode())
                {
                    // move
                    case KeyEvent.VK_LEFT:
                    case KeyEvent.VK_RIGHT:
                    case KeyEvent.VK_UP:
                    case KeyEvent.VK_DOWN:
                    {
                        world.getPlayer().setLookingDirectionByKeyCode(e.getKeyCode());
                        if (WALK_ON_ARROWKEY_INPUT)
                        {
                            action = ACTION_WALK;
                        }
                        break;
                    }
                    // open
                    case KeyEvent.VK_O:
                    {
                        action = ACTION_OPEN;
                        break;
                    }
                    // attack
                    case KeyEvent.VK_A:
                    {
                        action = ACTION_ATTACK;
                        break;
                    }
                    // run (its no action!)
                    // special keystroke ctrl+alt+r to resume if paused for no reason
                    case KeyEvent.VK_R:
                    {
                        if (e.isControlDown() && e.isAltDown())
                        {
                            resume();
                        }
                        else
                        {
                            action = ACTION_RUN;
                        }
                        break;
                    }
                    // walk (its no action!)
                    case KeyEvent.VK_W:
                    {
                        action = ACTION_WALK;
                        break;
                    }
                }
            }
        });
    }
    
    @Override
    protected void onStart()
    {
        // tile-size
        Graphics2D g = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).createGraphics();
        g.setFont(FONT);
        FontMetrics fm = g.getFontMetrics();
        TILE_WIDTH = fm.charWidth(Room.FLOOR);
        TILE_HEIGHT = fm.getHeight();
        Logger.trace("TILE-SIZE: ", TILE_WIDTH, "/", TILE_HEIGHT);
        // create interface
        ui = new Interface();
        ui.setLeft(0);
        ui.setRight(30);
        ui.setTop(0);
        ui.setBottom(10);
        ui.setRoomSize(48, 32);
        // check for savegames
        boolean loadSave = false;
        try
        {
            loadSave = SaveGame.getSaveFiles().length > 0;
        }
        catch (IOException ex)
        {
            Logger.error("Can not find save-files: ", ex);
        }
        if (loadSave)
        {
            load();
        }
        if (world == null)
        {
            // create world
            world = new World();
            // ask for player name
            do
            {
                String name = JOptionPane.showInputDialog("Enter Player Name:");
                if (name != null && name.length() > Player.MAX_NAME_LENGTH)
                {
                    Logger.showError("Name to long '" + name + "': " + name.length() + " > max " + Player.MAX_NAME_LENGTH + " chars");
                    name = null;
                }
                world.getPlayer().setName(name);
            }
            while (world.getPlayer().getName() == null);
            // load rooms
            URL[] rooms = new URL[] {
                     RPG.class.getResource("/rpg/Map0001.txt")
                    ,RPG.class.getResource("/rpg/Map0002.txt")
                    ,RPG.class.getResource("/rpg/Map0003.txt")
                    ,RPG.class.getResource("/rpg/Map0004.txt")
                    // cool rooms
                    ,RPG.class.getResource("/rpg/Room0001.txt")
            };
            for (URL url : rooms)
            {
                try
                {
                    Room room = Room.loadMap(Util.readTxtFile(url.openStream(), CHARSET));
                    String name = url.getFile();
                    name = name.substring(name.lastIndexOf("/"));
                    room.setName(name);
                    world.addRoom(room);
                    if (world.getCurrentRoom() == null)
                    {
                        world.setCurrentRoom(name);
                    }
                }
                catch (IOException ex)
                {
                    Logger.error("Load failed: ", url, ex);
                }
            }
            // force map load
            loadMap(false);
        }
        // set frame size
        Dimension newFrameSize = getImageSize();
        newFrameSize.width  += 10;
        newFrameSize.height += 40;
        setFrameSize(newFrameSize);
    }
    
    private void loadMap(boolean chooseFile)
    {
        pause();
        if (chooseFile)
        {
            JFileChooser fc = new JFileChooser(FileSystemView.getFileSystemView().getHomeDirectory());
            fc.setFileFilter(new FileNameExtensionFilter("Plain Text " + CHARSET, "txt"));
            if (fc.showOpenDialog(null) == JFileChooser.APPROVE_OPTION)
            {
                try
                {
                    Room room = Room.loadMap(Util.readTxtFile(new FileInputStream(fc.getSelectedFile()), CHARSET));
                    room.setName(fc.getSelectedFile().getName());
                    world.addRoom(room);
                    world.setCurrentRoom(room.getName());
                }
                catch (IOException ex)
                {
                    Logger.showExceptionError(ex);
                }
            }
        }
        else
        {
            world.selectRoom();
        }
        if (ui == null)
        {
            Logger.warn("!!! UI IS NULL !!! CAN NOT SET ROOM !!!");;
        }
        else
        {
            ui.setRoom(world.getCurrentRoom());
        }
        resume();
    }
    
    private void save(boolean quicksave)
    {
        pause();
        try
        {
            SaveGame.save(quicksave, world);
        }
        catch (IOException ex)
        {
            Logger.showExceptionError(ex);
        }
        resume();
    }
    
    private void load()
    {
        pause();
        boolean abort = false;
        do
        {
            try
            {
                File saveFile = (File) JOptionPane.showInputDialog(null, "Choose wisely padawan", "Customized Dialog", JOptionPane.PLAIN_MESSAGE, null, SaveGame.getSaveFiles(), null);
                if (saveFile != null)
                {
                    if (saveFile.length() == 0 && saveFile.delete() == false)
                    {
                        Logger.error("Can not delete file: ", saveFile.getAbsolutePath());
                    }
                    if (saveFile.exists())
                    {
                        world = SaveGame.load(saveFile);
                        if (world == null)
                        {
                            throw new IOException("Can not read file " + saveFile.getAbsolutePath() + "; size: " + saveFile.length());
                        }
                        if (world.getCurrentRoom() == null)
                        {
                            world.selectRoom();
                        }
                        ui.setRoom(world.getCurrentRoom());
                    }
                }
                else
                {
                    abort = true;
                }
            }
            catch (IOException ex)
            {
                Logger.showExceptionError(ex);
            }
        }
        while (world == null && abort == false);
        resume();
    }
    
    @Override
    protected void doNext()
    {
        // get current tick
        int currentTick = tick++ / TICK_FACTOR;
        // do next?
        if (currentTick > lastTick)
        {
            // check time
            long startTime = System.nanoTime();
            Logger.trace("TICK:", currentTick, " TIME: ", startTime);
            // do something?
            Room room = world.getCurrentRoom();
            if (room != null)
            {
                int x = room.getPlayerLookingPosition().x;
                int y = room.getPlayerLookingPosition().y;
                try
                {
                    if (action != ACTION_NOTHING)
                    {
                        switch (action)
                        {
                            case ACTION_ATTACK:
                            {
                                if (room.isMonsterPosition(x, y))
                                {
                                    Monster m = room.getMonster(x, y);
                                    if (m.isAlive())
                                    {
                                        Player p = world.getPlayer();
                                        m.setHealth(m.getHealth() - p.getAttack());
                                        p.setHealth(p.getHealth() - m.getAttack());
                                    }
                                }
                                break;
                            }
                            case ACTION_OPEN:
                            {
                                if (room.isDoor(x, y))
                                {
                                    Door door = room.getDoor(x, y);
                                    door.setOpen(true);
                                }
                                break;
                            }
                            // walk?
                            case ACTION_RUN:
                            case ACTION_WALK:
                            {
                                if (room.isAccessibleForPlayer(x, y))
                                {
                                    Point p = new Point(x, y);
                                    room.setPlayerPosition(p);
                                    world.setLastPlayerPosition(p);
                                    if (room.changeRoom())
                                    {
                                        Room nextRoom = room.getRoom(x, y);
                                        Logger.debug(nextRoom);
                                    }
                                }
                                else
                                {
                                    switch (room.get(x, y).getChar())
                                    {
                                        case Room.LAVA:
                                        {
                                            room.getPlayer().addBuff(new EnterFireBuff(currentTick, room.getPlayer()));
                                            break;
                                        }
                                        case Room.WATER:
                                        {
                                            room.getPlayer().addBuff(new EnterWaterBuff(currentTick, room.getPlayer()));
                                            break;
                                        }
                                    }
                                }
                                // do everything once except running
                                if (action != ACTION_RUN)
                                {
                                    action = ACTION_NOTHING;
                                }
                                break;
                            }
                        }
                    }
                }
                catch (IllegalArgumentException ex)
                {
                    Logger.warn("Illegal Position: ", x, "/", y);
                }
                // do tick stuff
                room.getPlayer().checkBuffs(currentTick);
                room.doFieldOfView();
            }
            // save tick
            setInfo("Tick: " + (lastTick = currentTick) + " - " + ((System.nanoTime() - startTime) / 1000) + " ms");
        }
    }
    
    @Override
    protected Dimension getImageSize()
    {
        if (ui == null)
        {
            return new Dimension(1,1);
        }
        return new Dimension(ui.getWidth() * TILE_WIDTH, ui.getHeight() * (TILE_HEIGHT - TILE_HEIGHT_RENDER_MINUS));
    }
    
    @Override
    protected BufferedImage getImage()
    {
        Logger.trace("PAINT");
        // refresh Interfaces
        if (isRunning())
        {
            ui.printBuffers();
        }
        // image
        Dimension imageSize = getImageSize();
        // image
        BufferedImage image = new BufferedImage(imageSize.width, imageSize.height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = image.createGraphics();
        g.setFont(FONT);
        // paint it
        for (int y = 0; y < ui.getHeight(); y++)
        {
            for (int x = 0; x < ui.getWidth(); x++)
            {
                ColoredChar cc = ui.get(x, y);
                // draw background
                g.setColor(cc.getBackground());
                g.fillRect(TILE_WIDTH * x, 
                           (y + 1) * (TILE_HEIGHT - TILE_HEIGHT_RENDER_MINUS) - (TILE_HEIGHT - 2 * TILE_HEIGHT_RENDER_MINUS), 
                           TILE_WIDTH, TILE_HEIGHT);
                // draw foreground
                g.setColor(cc.getForeground());
                g.drawString(String.valueOf(cc.getChar()), 
                             TILE_WIDTH * x,
                             (y + 1) * (TILE_HEIGHT - TILE_HEIGHT_RENDER_MINUS));
            }
        }
        return image;
    }
    
    public static void main(String[] args)
    {
        try
        {
            final Thread t = new Thread(new RPG(), NAME);
            t.start();
        }
        catch (Exception ex)
        {
            Logger.showExceptionError(ex);
        }
    }
}
